#include "HSModelAPI.h"
#include "WorldInfoController.h"
#include "Ground.h"
#include "SharedConstants.h"
#include "HeightMapCoordinates.h"
#include "Human.h"
#include "Water.h"
#include "BalloonsShop.h"
#include "Bench.h"
#include "Tree.h"
#include "FerrisWheel.h"
#include "GroundTypes.h"

#include <set>

namespace
  {
  /**
  *  Converts heightmap points containing water to a set of cells affected by water. i_size{x, y} - count of
  *  cells
  */
  std::set<TCell> ConvertWaterAreaToCellMapping(const THMapArea& i_area, size_t i_size_x, size_t i_size_y)
    {
    std::set<TCell> result;
    for(auto point : i_area.points)
      {
      if(point.first < i_size_x && point.second < i_size_y)
        result.insert(TCell(point.first, point.second));
      if(point.first > 0 && point.second > 0)
        result.insert(TCell(point.first - 1, point.second - 1));
      if(point.first > 0 && point.second < i_size_y)
        result.insert(TCell(point.first - 1, point.second));
      if(point.first < i_size_x && point.second > 0)
        result.insert(TCell(point.first, point.second - 1));
      }

    return result;
    }

  std::vector<TCell> GetCellNeighbours(const TCell& i_cell)
    {
    std::vector<TCell> cells;
    
    cells.push_back(TCell(i_cell.first - 1, i_cell.second));
    cells.push_back(TCell(i_cell.first, i_cell.second - 1));
    cells.push_back(TCell(i_cell.first + 1, i_cell.second));
    cells.push_back(TCell(i_cell.first, i_cell.second + 1));

    return cells;
    }
  }

/**
*  Converts double real-world 2D point coords to cell where this points is located
*/
TCell ConvertTVectorToTCell(const TVector2& i_vector)
  {
  std::pair<size_t, size_t> coords = HeightMapUtils::FindHeightMapIndexes(i_vector);
  if(coords.first == GroundConstants::CELL_COUNT)
    coords.first--;
  if(coords.second == GroundConstants::CELL_COUNT)
    coords.second--;

  return TCell(coords);
  }

WorldInfoController::WorldInfoController(Ground& i_ground, std::map<std::string, IRenderable*>& i_objects) 
  : m_ground(i_ground)
  {
  UpdateGroundInfo();
  UpdateWaterInfo();
  UpdateInfo(i_objects);
  };

WorldInfoController::~WorldInfoController()
  {

  }

void WorldInfoController::UpdateGroundInfo()
  {
  const TGroundTypeMap& gtm = m_ground.GetGroundTypeMap();
  for(size_t x = 0; x < gtm.size(); x++)
    {
    for(size_t y = 0; y < gtm[x].size(); y++)
      {
			_SynchronizeGroundTypesAndWorldEntities(TCell(x,y), gtm[x][y]);
      }
    }
  }

void WorldInfoController::UpdateWaterInfo()
  {
  auto waters = m_ground.GetWaterModels();
  for(auto it : waters)
    {
    AddWater(it);
    }
  }

void WorldInfoController::SetGroundType(const TVector2& i_point, EGroundType i_type)
  {
  TCell cell = ConvertTVectorToTCell(i_point);
	_SynchronizeGroundTypesAndWorldEntities(cell, i_type);
  }

void WorldInfoController::UpdateInfo(std::map<std::string, IRenderable*>& i_objects)
  {
  m_objects_positions.clear();

  for(auto it : i_objects)
    {
    TCell coords = ConvertTVectorToTCell(TVector2(it.second->GetPosition().X(), it.second->GetPosition().Y()));
    auto x = coords.first;
    auto y = coords.second;

    if(auto obj = dynamic_cast<Tree*>(it.second))
      m_world_entities[TCell(x, y)] = WE_TREE;
    if(auto obj = dynamic_cast<FerrisWheel*>(it.second))
      {
      for (size_t ind_x = x-1; ind_x <= x + 1; ++ind_x)
        for (size_t ind_y = y - 1; ind_y <= y + 1; ++ind_y)
          {
          m_world_entities[TCell(ind_x, ind_y)] = WE_FERRIS;
          m_objects_positions[TCell(ind_x, ind_y)] = it.second;
          }          
      }
    if(auto obj = dynamic_cast<BalloonsShop*>(it.second))
      m_world_entities[TCell(x, y)] = WE_BALLOONS_SHOP;

    if(auto obj = dynamic_cast<Bench*>(it.second))
      m_world_entities[TCell(x, y)] = WE_BENCH;

    if (auto obj = dynamic_cast<Human*>(it.second))
      continue;
    m_objects_positions[coords] = it.second;
    }
  }

double WorldInfoController::GetHeightForXY(TVector2 i_point) const
  {
  return m_ground.GetHeightForXY(i_point);
  }

bool WorldInfoController::AvailableForWalking(const std::string& i_name, TVector2 i_point) const
  {
  // Checking for out of world range
  double max_x = GroundConstants::CELL_COUNT * GroundConstants::CELL_SIZE;
  double max_y = GroundConstants::CELL_COUNT * GroundConstants::CELL_SIZE;

  if(i_point.X() < 0 || i_point.Y() < 0 || i_point.X() >= max_x || i_point.Y() >= max_y)
    {
    return false;
    }

  //Checking for walkway
  TCell cell = HeightMapUtils::FindHeightMapIndexes(i_point);
  auto it = m_world_entities.find(cell);
  if (it != m_world_entities.end() && it->second != EWorldEntityTypes::WE_WALKWAY)
    {
    return false;
    }

  //Checking for people
  for(auto it : m_human_positions)
    {
    if(i_name == it.first)
      continue;

    if(cell == it.second)
      {
      return false;
      }
    }

  return true;
  }

void WorldInfoController::SetHumanPosition(const std::string& i_name, TVector3 i_position)
  {
  TCell cell = ConvertTVectorToTCell(TVector2(i_position.X(), i_position.Y()));
  m_human_positions[i_name] = cell;
  }

bool WorldInfoController::IsCellFree(TCell cell) const
  {  
  for(auto it : m_human_positions)
    {
    if(it.second == cell)
      return false;
    }
  auto entity_it = m_world_entities.find(cell);
  if (entity_it != m_world_entities.end() && entity_it->second == EWorldEntityTypes::WE_WATER)
    {
    return false;
    }
  if(m_objects_positions.count(cell) == 0)
    return true;
  else
    return false;
  }

WorldObject* WorldInfoController::GetObjectByPosition(const TVector2& i_position)
  {
  TCell cell = ConvertTVectorToTCell(i_position);
  if(!IsCellFree(cell)) 
    return dynamic_cast<WorldObject*>(m_objects_positions[cell]);
  else
    return nullptr;
  }

void WorldInfoController::DeleteHumanPosition(const std::string& i_name)
  {
  auto it = m_human_positions.find(i_name);
  if(it != m_human_positions.end())
    m_human_positions.erase(it);
  }

bool WorldInfoController::GetEntityOnCell(EWorldEntityTypes& io_result, const TCell& cell) const
  {
  if (!m_world_entities.count(cell))
    return false;
  io_result = m_world_entities.at(cell);
  return true;
  }
void WorldInfoController::_SynchronizeGroundTypesAndWorldEntities(TCell i_cell, EGroundType i_ground_type)
	{
	if (i_ground_type == GT_WALKWAY)
		m_world_entities[i_cell] = WE_WALKWAY;
	else if (i_ground_type == GT_GRASS)
		m_world_entities[i_cell] = WE_GRASS;
	}

void WorldInfoController::RemoveWater(std::shared_ptr<Water> i_water)
  {
  const THMapArea& area = i_water->GetArea();
  std::set<TCell> cells = ConvertWaterAreaToCellMapping(area, GroundConstants::CELL_COUNT,
    GroundConstants::CELL_COUNT);

  for(auto cell : cells)
    {
		EGroundType ground_type = m_ground.GetGroundType(cell.first, cell.second);
		_SynchronizeGroundTypesAndWorldEntities(cell, ground_type);
    }
  }

void WorldInfoController::AddWater(std::shared_ptr<Water> i_water)
  {
  const THMapArea& area = i_water->GetArea();
  std::set<TCell> cells = ConvertWaterAreaToCellMapping(area, GroundConstants::CELL_COUNT,
    GroundConstants::CELL_COUNT);

  for(auto cell : cells)
    {
    m_world_entities[cell] = WE_WATER;
    }
  }

bool WorldInfoController::CanHumanAcquireBalloon(const TVector3& i_position)
  {
  TCell human_cell = ConvertTVectorToTCell(TVector2(i_position.X(), i_position.Y()));

  for(TCell cell : GetCellNeighbours(human_cell))
    {
    if(m_world_entities.count(cell) > 0)
      if(m_world_entities[cell] == WE_BALLOONS_SHOP)
        return true;
    }

  return false;
  }

void WorldInfoController::RemoveObject(const WorldObject *i_object)
  {
  TCell cell = ConvertTVectorToTCell(TVector2(i_object->GetPosition().X(), i_object->GetPosition().Y()));
  size_t x = cell.first;
  size_t y = cell.second;
  if(auto obj = dynamic_cast<const FerrisWheel*>(i_object))
    {
    for (size_t ind_x = x - 1; ind_x <= x + 1; ++ind_x)
      for (size_t ind_y = y - 1; ind_y <= y + 1; ++ind_y)
        {
        m_world_entities[TCell(ind_x, ind_y)] = WE_GRASS;
        }          
    }
  else
    {
    m_world_entities[cell] = WE_GRASS;
    }
  }
